/*
 * Copyright (c) 2001-2008 Caucho Technology, Inc.  All rights reserved.
 *
 * The Apache Software License, Version 1.1
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:
 *       "This product includes software developed by the
 *        Caucho Technology (http://www.caucho.com/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "Hessian", "Resin", and "Caucho" must not be used to
 *    endorse or promote products derived from this software without prior
 *    written permission. For written permission, please contact
 *    info@caucho.com.
 *
 * 5. Products derived from this software may not be called "Resin"
 *    nor may "Resin" appear in their names without prior written
 *    permission of Caucho Technology.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL CAUCHO TECHNOLOGY OR ITS CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @author Scott Ferguson
 */

package com.caucho.hessian.server;

import com.caucho.hessian.io.*;
import com.caucho.services.server.GenericService;
import com.caucho.services.server.Service;
import com.caucho.services.server.ServiceContext;

import javax.servlet.GenericServlet;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.logging.*;

/**
 * Servlet for serving Hessian services.
 */
public class HessianServlet extends GenericServlet {
    private Logger _log = Logger.getLogger(HessianServlet.class.getName());

    private Class _homeAPI;
    private Object _homeImpl;

    private Class _objectAPI;
    private Object _objectImpl;

    private HessianSkeleton _homeSkeleton;
    private HessianSkeleton _objectSkeleton;

    private SerializerFactory _serializerFactory;

    private boolean _isDebug;

    public HessianServlet() {
    }

    public String getServletInfo() {
        return "Hessian Servlet";
    }

    /**
     * Sets the home api.
     */
    public void setHomeAPI(Class api) {
        _homeAPI = api;
    }

    /**
     * Sets the home implementation
     */
    public void setHome(Object home) {
        _homeImpl = home;
    }

    /**
     * Sets the object api.
     */
    public void setObjectAPI(Class api) {
        _objectAPI = api;
    }

    /**
     * Sets the object implementation
     */
    public void setObject(Object object) {
        _objectImpl = object;
    }

    /**
     * Sets the service class.
     */
    public void setService(Object service) {
        setHome(service);
    }

    /**
     * Sets the api-class.
     */
    public void setAPIClass(Class api) {
        setHomeAPI(api);
    }

    /**
     * Gets the api-class.
     */
    public Class getAPIClass() {
        return _homeAPI;
    }

    /**
     * Sets the serializer factory.
     */
    public void setSerializerFactory(SerializerFactory factory) {
        _serializerFactory = factory;
    }

    /**
     * Gets the serializer factory.
     */
    public SerializerFactory getSerializerFactory() {
        if (_serializerFactory == null)
            _serializerFactory = new SerializerFactory();

        return _serializerFactory;
    }

    /**
     * Sets the serializer send collection java type.
     */
    public void setSendCollectionType(boolean sendType) {
        getSerializerFactory().setSendCollectionType(sendType);
    }

    /**
     * Sets the debugging flag.
     */
    public void setDebug(boolean isDebug) {
        _isDebug = isDebug;
    }

    /**
     * Sets the debugging log name.
     */
    public void setLogName(String name) {
        _log = Logger.getLogger(name);
    }

    /**
     * Initialize the service, including the service object.
     */
    public void init(ServletConfig config) throws ServletException {
        super.init(config);

        try {
            if (_homeImpl != null) {
            } else if (getInitParameter("home-class") != null) {
                String className = getInitParameter("home-class");

                Class homeClass = loadClass(className);

                _homeImpl = homeClass.newInstance();

                init(_homeImpl);
            } else if (getInitParameter("service-class") != null) {
                String className = getInitParameter("service-class");

                Class homeClass = loadClass(className);

                _homeImpl = homeClass.newInstance();

                init(_homeImpl);
            } else {
                if (getClass().equals(HessianServlet.class))
                    throw new ServletException("server must extend HessianServlet");

                _homeImpl = this;
            }

            if (_homeAPI != null) {
            } else if (getInitParameter("home-api") != null) {
                String className = getInitParameter("home-api");

                _homeAPI = loadClass(className);
            } else if (getInitParameter("api-class") != null) {
                String className = getInitParameter("api-class");

                _homeAPI = loadClass(className);
            } else if (_homeImpl != null) {
                _homeAPI = findRemoteAPI(_homeImpl.getClass());

                if (_homeAPI == null)
                    _homeAPI = _homeImpl.getClass();
            }

            if (_objectImpl != null) {
            } else if (getInitParameter("object-class") != null) {
                String className = getInitParameter("object-class");

                Class objectClass = loadClass(className);

                _objectImpl = objectClass.newInstance();

                init(_objectImpl);
            }

            if (_objectAPI != null) {
            } else if (getInitParameter("object-api") != null) {
                String className = getInitParameter("object-api");

                _objectAPI = loadClass(className);
            } else if (_objectImpl != null)
                _objectAPI = _objectImpl.getClass();

            _homeSkeleton = new HessianSkeleton(_homeImpl, _homeAPI);
            if (_objectAPI != null)
                _homeSkeleton.setObjectClass(_objectAPI);

            if (_objectImpl != null) {
                _objectSkeleton = new HessianSkeleton(_objectImpl, _objectAPI);
                _objectSkeleton.setHomeClass(_homeAPI);
            } else
                _objectSkeleton = _homeSkeleton;

            if ("true".equals(getInitParameter("debug")))
                _isDebug = true;

            if ("false".equals(getInitParameter("send-collection-type")))
                setSendCollectionType(false);
        } catch (ServletException e) {
            throw e;
        } catch (Exception e) {
            throw new ServletException(e);
        }
    }

    private Class findRemoteAPI(Class implClass) {
        if (implClass == null || implClass.equals(GenericService.class))
            return null;

        Class[] interfaces = implClass.getInterfaces();

        if (interfaces.length == 1)
            return interfaces[0];

        return findRemoteAPI(implClass.getSuperclass());
    }

    private Class loadClass(String className) throws ClassNotFoundException {
        ClassLoader loader = getContextClassLoader();

        if (loader != null)
            return Class.forName(className, false, loader);
        else
            return Class.forName(className);
    }

    protected ClassLoader getContextClassLoader() {
        return Thread.currentThread().getContextClassLoader();
    }

    private void init(Object service) throws ServletException {
        if (!this.getClass().equals(HessianServlet.class)) {
        } else if (service instanceof Service)
            ((Service) service).init(getServletConfig());
        else if (service instanceof Servlet)
            ((Servlet) service).init(getServletConfig());
    }

    /**
     * Execute a request.  The path-info of the request selects the bean.
     * Once the bean's selected, it will be applied.
     */
    public void service(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;

        if (!req.getMethod().equals("POST")) {
            res.setStatus(500, "Hessian Requires POST");
            PrintWriter out = res.getWriter();

            res.setContentType("text/html");
            out.println("<h1>Hessian Requires POST</h1>");

            return;
        }

        String serviceId = req.getPathInfo();
        String objectId = req.getParameter("id");
        if (objectId == null)
            objectId = req.getParameter("ejbid");

        ServiceContext.begin(req, serviceId, objectId);

        try {
            InputStream is = request.getInputStream();
            OutputStream os = response.getOutputStream();

            response.setContentType("application/x-hessian");

            SerializerFactory serializerFactory = getSerializerFactory();

            invoke(is, os, objectId, serializerFactory);
        } catch (RuntimeException e) {
            throw e;
        } catch (ServletException e) {
            throw e;
        } catch (Throwable e) {
            throw new ServletException(e);
        } finally {
            ServiceContext.end();
        }
    }

    protected void invoke(InputStream is, OutputStream os, String objectId, SerializerFactory serializerFactory) throws Exception {
        if (objectId != null)
            _objectSkeleton.invoke(is, os, serializerFactory);
        else
            _homeSkeleton.invoke(is, os, serializerFactory);
    }

    protected Hessian2Input createHessian2Input(InputStream is) {
        return new Hessian2Input(is);
    }

    static class LogWriter extends Writer {
        private Logger _log;
        private StringBuilder _sb = new StringBuilder();

        LogWriter(Logger log) {
            _log = log;
        }

        public void write(char ch) {
            if (ch == '\n' && _sb.length() > 0) {
                _log.fine(_sb.toString());
                _sb.setLength(0);
            } else
                _sb.append((char) ch);
        }

        public void write(char[] buffer, int offset, int length) {
            for (int i = 0; i < length; i++) {
                char ch = buffer[offset + i];

                if (ch == '\n' && _sb.length() > 0) {
                    _log.fine(_sb.toString());
                    _sb.setLength(0);
                } else
                    _sb.append((char) ch);
            }
        }

        public void flush() {
        }

        public void close() {
        }
    }
}
